package mbsl.commons.games;

import java.util.ArrayList;
import java.util.List;

import mbsl.commons.Commons;
import mbsl.commons.base.BasicElementController;
import mbsl.commons.base.ElementEditController;
import mbsl.commons.games.MobyGamesMiner.MobyDetails;
import mbsl.commons.games.MobyGamesMiner.MobyHit;
import mbsl.commons.lib.BasicElement;
import mbsl.commons.lib.Game;
import mbsl.commons.lib.GameGenre;
import mbsl.commons.lib.Location;
import mbsl.main.StatusBarController;
import mbsl.storage.Database;
import mbsl.system.TextProvider;
import mbsl.ui.dialogs.Dialogs;
import mbsl.ui.interfaces.Listener;
import mbsl.util.SWTUtils;

import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Shell;

public class GameEditController extends ElementEditController {
	public GameEditController(GameController pParent, Shell pParentWindow) {
		super(pParent.getCore().getDatabase());
		
		mDialog = new GameEditDialog(pParentWindow);
		mParent = pParent;
		mDatabase = mParent.getCore().getDatabase();

		mDialog.setTitleShadowText(tp.get("title.shadow"));
		
		mGenreController = new BasicElementController<GameGenre>(mDatabase, GameGenre.class);
		mPredecessorController = new BasicElementController<Game>(mDatabase, Game.class);
		mAdvancedSearchController = new AdvancedSearchController<Game>(
				pParent.getCore(),
				Game.class,
				mDialog.getPredecessor(), // <-- The widget to which it's attached
				mDialog.getStatusBar(),
				tp, 
				"lower(name) like '%%%s%%'")
		;
		
		// Action listeners
		//  Dialogue save button
		mDialog.addSaveListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent pE) {
				if (save()) {
					for (Listener listener : mListeners)
						listener.alert();
					
					mDialog.close();
				}
			}
		});
		//  Dialogue fetch button
		mDialog.addMobyFetchListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent pE) {
				fetch();
			}
		});
		//  Dialogue closing
		mDialog.addCloseListener(new Listener() {
			@Override
			public void alert() {
				if (mElement.getName() != null)
					mDialog.getPredecessor().add(mElement);
			}
		});
		mDialog.getTitleField().addModifyListener(new ModifyListener() {
			@Override
			public void modifyText(ModifyEvent pE) {
				
			}
		});
		
		// Add data to the combo boxes
		mGenreController.addCombo(mDialog.getGenre());
		mPredecessorController.addCombo(mDialog.getPredecessor());
		getLocationController().addCombo(mDialog.getLocation());
	}
	/** The same as calling setGame(null) */
	public void newGame() {
		setGame(null);
	}
	/** Notifies the EditController that a game is removed, so that is can update its lists. */
	public void notifyRemoved(Game pGame) {
		mDialog.getPredecessor().remove(pGame);
	}
	/**
	 * Puts the data from the passed Game into the dialogue.
	 * <p>
	 * If {@code null} is passed, a new game will be created, and the fields will be blank.
	 */
	public void setGame(Game pGame) {
		if (pGame == null)
			mElement = new Game();
		else
			mElement = pGame;

		mAdvancedSearchController.setElement(mElement);
		
		if (mElement.getName() == null || mElement.getName().isEmpty())
			mDialog.setWindowTitle(tp.get("title.new"));
		else
			mDialog.setWindowTitle(tp.get("title"));
		
		// Set up the fields
		mDialog.setName(mElement.getName());
		
		if (mElement.getLocation() != null)
			mDialog.getLocation().setSelected(mElement.getLocation());
		else // If there's no recorded previous location, there will be no selection (null)
			mDialog.getLocation().setSelected(mPreviousLocation);
		
		// In several places here, we're taking advantage of the UI elements (or their wrappers) being able to take null as not set.
		mDialog.getPredecessor().setSelected(mElement.getParent());
		mDialog.getGenre().setSelected(mElement.getGenre());
		mDialog.getRating().setValue(mElement.getRating() > 0 ? mElement.getRating() : 0); // Compatibility. Previously negative values meant not set.
		mDialog.getYear().setValue(mElement.getYear() != null ? Integer.parseInt(mElement.getYear()) : null);
		mDialog.getMobyScore().setValue(mElement.getMobyScore());

		if (mElement.getName() != null)
			mDialog.getPredecessor().remove(mElement);
		
		mDialog.getStatusBar().setInfo(Commons.tp.get("status.ready"));
	}
	public Game getGame() {
		return mElement;
	}
	/** Attempts to save the data. Returns false if something went wrong. */
	private boolean save() {
		if (mDialog.getName().trim().isEmpty()) {
			Dialogs.error(mDialog.getShell(), Commons.tp.get("error.noname"), Commons.tp.get("error.nonameShort"));
			return false;
		}
		
		// Name
		mElement.setName(mDialog.getName());
		// Location
		mElement.setLocation(getLocationController().getBasicElement(mDialog.getLocation().getSelected()));
		// Genre
		mElement.setGenre(mGenreController.getBasicElement(mDialog.getGenre().getSelected()));
		// Predecessor
		if (!(mDialog.getPredecessor().getSelected() instanceof String))
			mElement.setParent(mPredecessorController.getBasicElement(mDialog.getPredecessor().getSelected()), true);
		// Rating
		mElement.setRating(mDialog.getRating().getValue());
		// Year
		mElement.setYear(mDialog.getYear().getValue() != null ? mDialog.getYear().getValue().toString() : null);
		// MobyScore
		mElement.setMobyScore(mDialog.getMobyScore().getValue() != null ? mDialog.getMobyScore().getValue() : null);
		
		mPreviousLocation = mElement.getLocation();
		
		mDatabase.save(mElement);

		mDialog.getPredecessor().add(mElement);
		
		return true;
	}
	/** Runs the actions associated with the MobyGames Fetch button in a separate thread. */
	private void fetch() {
		final StatusBarController sbc = mDialog.getStatusBar();
		final String searchText = mDialog.getName();
			
		sbc.setInfo(tp.get("moby.connect"));
		sbc.setProgress(0);

		new Thread(new Runnable() {
			private Integer mHit;
			
			public void run() {
				MobyGamesMiner miner = new MobyGamesMiner();
				
				final List<MobyHit> results = miner.search(searchText);

				if (results == null) {
					SWTUtils.exec(mDialog.getShell().getDisplay(), new Runnable() {
						public void run() {
							sbc.finishAndRelease(tp.get("moby.connectError"));
						}
					});
					return;
				}

				switch (results.size()) {
				case 0:
					SWTUtils.exec(mDialog.getShell().getDisplay(), new Runnable() {
						public void run() {
							sbc.finishAndRelease(tp.get("moby.nohits"));
						}
					});
					return;
				case 1:
					mHit = 0;
					break;
				default:
					SWTUtils.blockExec(mDialog.getShell().getDisplay(), new Runnable() {
						public void run() {
							mHit = Dialogs.selector(mDialog.getShell(), results, tp.get("moby.select"), tp.get("moby.selectShort"));
						}
					});

					if (mHit == null) {
						SWTUtils.exec(mDialog.getShell().getDisplay(), new Runnable() {
							public void run() {
								sbc.finishAndRelease(tp.get("moby.cancel"));
							}
						});
						return;
					}
					break;
				}

				final MobyDetails details = miner.get(mHit);
				
				if (details != null)
					SWTUtils.exec(mDialog.getShell().getDisplay(), new Runnable() {
						public void run() {
							if (details.getYear() != null)
								mDialog.getYear().setValue(Integer.parseInt(details.getYear()));
							
							mDialog.getMobyScore().setValue(details.getMobyScore());
							
							sbc.finishAndRelease(tp.get("moby.success"));
						}
					});
				else
					sbc.finishAndRelease(tp.get("moby.fail"));
			}
		}).start();
	}
	/**
	 * Convenience method for fetching a database listing of class <i>pClass</i> in order <i>pOrderString</i>
	 * @param pClass The class of the objects to list.  Must extend BasicElement.
	 * @param pOrderString The order in which to sort.  Takes HQL, and 'order by' is already included.
	 */
	protected List<?> getElementList(Class<? extends BasicElement> pClass, String pArguments) {
		return mParent.getCore().getDatabase().getList(pClass, pArguments);
	}
	/**
	 * Convenience method for fetching a database listing of class <i>pClass</i> ordered by name
	 * @param pClass The class of the objects to list.  Must extend BasicElement.
	 */
	protected List<?> getElementList(Class<? extends BasicElement> pClass) {
		return getElementList(pClass, "order by lower(name)");
	}
	public void addChangeListener(Listener pListener) {
		if (mListeners == null)
			mListeners = new ArrayList<Listener>();
		
		mListeners.add(pListener);
	}
	public void open() {
		mDialog.open();
	}
	
	private BasicElementController<GameGenre> mGenreController;
	private BasicElementController<Game> mPredecessorController;
	
	private AdvancedSearchController<Game> mAdvancedSearchController;
	private Database mDatabase;
	private List<Listener> mListeners;
	private TextProvider tp = Commons.tp.prefix("games.edit");
	private Location mPreviousLocation;
	private Game mElement;
	private GameController mParent;
	private GameEditDialog mDialog;
}
